home *** CD-ROM | disk | FTP | other *** search
/ Mac Magazin/MacEasy 30 / Mac Magazin and MacEasy Magazine CD - Issue 30.iso / utilities / Mac OS / Closed Folders / CF Extension Technical Details next >
Text File  |  2001-12-15  |  18KB  |  110 lines

  1.  
  2. Closed Folders Extension: Technical Details
  3. © Nyanza Software (Paul Crawford), 2000—2001
  4. WWW    : <http://homepage.mac.com/pcrawford/>
  5. E-mail: <mailto:pcrawford@mac.com?subject=Closed Folders utility>
  6.  
  7.  
  8. Table of Contents
  9. • Overview
  10. • Details
  11. • Afterword
  12.  
  13.  
  14.                                                                                                                                                    
  15. Overview
  16.  
  17.  
  18. This document will be of interest only if you want/need to have a fairly detailed idea of how the CF Extension works "under the hood". If you don't really care about this aspect, then you should become acquainted with the companion CF 'Read Me' document instead. The latter document primarily describes the operation of the CF control panel (which lets you adjust the settings that are used by the CF Extension, and also turn if 'Off' or 'On' as desired).
  19.  
  20. As mentioned in the CF 'Read Me' document, the CF Extension is a "combo" file that internally consists of two portions:- [1] an INIT (and its associated custom patch routines); and [2] a background-only application (BOA). Currently, the INIT portion is the only important one. {As explained later below, the BOA portion does absolutely nothing in this version of the CF utility.}
  21.  
  22. The INIT works invisibly behind the scenes, serving as a central repository of shared global data. For instance, the INIT retains the current CF preferences in memory. More importantly, however, it also installs custom patches for two of the standard routines in the Mac OS' Window Manager Application Programming Interface (API), or WAPI].  The Mac OS WAPI includes a wide variety of routines. Some of these, such as the old familiar ShowWindow and CloseWindow routines, are dispatched via the standard Trap Manager tables (and are therefore patchable by third parties); however, many others are not.
  23.  
  24. For example, the modern version of the WAPI (introduced in Mac OS 8.5) has several new routines which are not easily patchable by anyone other than Apple. These new routines are PowerPC-only, and reside in a shared code fragment named WindowsLib. The WindowsLib code fragment is not contained in a separate shared-library file; instead, it resides in the system's "master" shared-library container or ROM (and updated versions of its routines may also reside in the data fork of the System file (e.g., indexed via the 'cfrg' resource with ID 49).
  25.  
  26. Some of the new WAPI routines are specifically designed for getting and setting the "proxy" (or path) information for new-style windows. These modern windows have cute little proxy icons that allow one to do some nifty things, such as moving a window's associated file without taking a trip to the Finder. Tom Bender's popular shareware word processor, Tex-Edit Plus, is a good example of an app that uses such windows. Moreover, ever since Mac OS 8.5, the Finder itself is another app that uses such windows to display the contents of open folders!
  27.  
  28. So, for instance, if we could "hook" into certain new WAPI routines that are used by the Finder when creating/displaying a new folder window, such as SetWindowProxyFSSpec, this would allow us to intercept (and "cache" or store somewhere in memory) the location information for the window's associated folder. [This information is encoded in the FSSpec record that's supplied to the routine by the Finder.] Now, we already know that we can hook into certain familiar old WAPI routines, such as CloseWindow. Thus, whenever we detect that a folder window is closing, its location information (that we previously cached ) could be used to create an alias-file that's targeted to that window's associated folder. Hence, we would have a robust way of "remembering" closed folder windows so that they could be easily re-opened.
  29.  
  30. Well, that's the ideal situation… Unfortunately, as discussed above, since the modern WAPI routines such as SetWindowProxyFSSpec are not patchable via the traditional trap tables, we would need to resort to quite desperate measures. For example, we could bravely try to rewrite the entire WindowsLib code fragment from scratch and ship it as a shared-library to override the system's built-in version. Or, we could dream about directly modifying the System file to patch in our own routines. (!)
  31.  
  32. However, if sanity is to prevail, a somewhat different technique would be necessary. ;-)  The approach which we eventually adopted was to place all the burden entirely on the shoulders of a complementary "pair" of old, trappable WAPI routines — viz., the very same ShowWindow and CloseWindow routines, each of which is supplied with a WindowRef (or "window pointer") as its sole parameter by whoever invokes it. We chose to patch these two routines because they are almost always involved in the opening or closing of any window, including a Finder folder window. The ShowWindow routine is typically used to display a window the very first time after creating it (via the GetNewCWindow or NewCWindow routine), whereas the CloseWindow routine is used to close a window when it's no longer needed. To minimise the performance hit of intercepting such frequently used routines, our custom patches perform quick verification checks before doing any time-consuming tasks such as attempting to retrieve folder information and to create a corresponding alias file. Specifically, the patches verify that the Finder is the current  process (whether foreground or background), and that the supplied window is a standard document window equipped with a close-box, zoom-box, etc. (to weed out dialogues, 'Get Info' windows, floating windows and so on). If these conditions are not satisfied, the patches immediately "fall through" to the original routines.
  33.  
  34. Whenever an application calls ShowWindow, our custom ShowWindow patch gets control. It verifies the Finder-currency and the window-type, and then calls GetWindowProxyFSSpec (the info-retrieval counterpart of SetWindowProxyFSSpec) to obtain the window's associated folder location information. It stores this location information into a shared memory cache, and then calls the original ShowWindow routine to actually display the window.
  35.  
  36. Likewise, whenever an application calls CloseWindow, our custom CloseWindow patch gets control. It too verifies the Finder-currency and the window-type, and then searches in the shared memory cache for the window's associated folder location information. If it finds the location information, it creates the corresponding alias file (in the current "alias cache" folder as designated by the CF preferences), deletes the now-obsolete location information from the cache, and then calls the original CloseWindow routine to actually remove the window from the screen.
  37.  
  38. [NOTE: We use a pair of complementary patches plus a cache because relying on a single CloseWindow patch alone is not such a good idea. The first version (1.0) of the CF utility had taken that simpler approach, but suffered from many annoying glitches. Chief among them was a tendency to cause (re)login dialogues (or disk-insertion prompts) to appear when unmounting any remote (or removable) volume which had open windows.]
  39.  
  40. Fortunately, it seems to be perfectly permissible to make calls to (at least some of) the new WAPI routines from within the old WAPI routines. In other words, when from within our custom ShowWindow patch we call GetWindowProxyFSSpec, it does not apparently lead to any "reentrancy" issues in the Window Manager that could destabilise the Mac OS. This is probably because GetWindowProxyFSSpec just retrieves information directly from an internal window record. In addition, it's probably now crystal clear why the CF utility requires Mac OS 8.5 or later (although not including Mac OS X); it's simply because the new WAPI routines such as GetWindowProxyFSSpec aren't available in earlier Mac OS Classic versions. ;-)
  41.  
  42. On the face of it, this scheme of using a pair of custom ShowWindow and CloseWindow patches appears promising. The only major potential pitfall would arise if we were to ever accidentally access or use the current process' heap while we're performing an operation that allocates memory. [For example, when our CloseWindow patch calls NewAlias to create a new alias record for a target folder.] To avoid this issue, we explicitly set the current heap zone to be the System Heap when "entering" our ShowWindow or CloseWindow patch, and then restore it to be the original heap zone just before calling the original ShowWindow or CloseWindow routine and "exiting".
  43.  
  44.                                                                                                                                                    
  45. Details
  46.  
  47. OK, so we finally decided to let our pair of custom  ShowWindow and CloseWindow patches do all the work. So, how do they actually do it? And what about rest of the INIT portion, or the other BOA portion of the CF Extension?
  48.  
  49. Each major portion of the CF Extension is discussed in its respective subsection below.
  50.  
  51. The BOA
  52.  
  53. The BOA was originally designed to the perform the retrieval of the location information for folder windows — i.e., to make the calls to GetWindowProxyFSSpec. The ShowWindow patch would first perform the usual verification checks, and then signal the BOA that an eligible new window had arrived (e.g., by placing the supplied WindowRef into a "request queue" in the INIT's shared global data in memory). The BOA would then detect this signal during its event loop, proceed to call GetWindowProxyFSSpec, and cache the obtained information in memory; thus, everything would be ready and waiting for the CloseWindow patch whenever it detected a closing window. {Naturally, all of this was plotted & schemed before we had determined empirically that it was acceptable to call GetWindowProxyFSSpec directly from within our ShowWindow patch. ;-)}
  54.  
  55. Another task originally assigned to the BOA was the sending of Apple Events to the Finder, asking it to create the corresponding alias files (this is the only Apple-approved method for creating such files). Unfortunately, the "Finder-as-AppleEvent-server" technique turns out to be rather slow, especially if the Finder is busy (e.g., if there are many threads working on long copy operations or similar tasks). In the future, as the Finder's performance improves, it might become feasible to do things the official way.
  56.  
  57. For now, our custom ShowWindow and CloseWindow patches, respectively, perform these tasks that were formerly assigned to the BOA. In other words, the custom ShowWindow patch directly calls GetWindowProxyFSSpec, and then itself caches the obtained location information. Similarly, the CloseWindow patch itself looks up the location information in the shared cache, and then directly creates the corresponding alias file. To make a long story short, the BOA code currently just quits immediately as soon as it's loaded by the Process Manager (towards the end of the bootup stage).
  58.  
  59. The INIT
  60.  
  61. Firstly, the INIT code resource is marked with both the 'System' and 'Locked' flags, so that it will be automatically loaded into the System Heap at bootup, and locked so that it cannot move in memory. We do this because we need to guarantee the "always resident" nature (and the fixed addresses) of various shared globals and auxiliary routines that are "installed" by the INIT.
  62.  
  63. One of these auxiliary routines is used solely for Multiple Users support under Mac OS 9. It is installed as a Folder Manager notification "callback" routine. This lets us detect when users log out or in, so that we can re-read the "active" preferences file.
  64.  
  65. The other auxiliary routine is installed as a custom Gestalt routine. This allows the other CF units to obtain the addresses of the shared global data by simply issuing a call to Gestalt, supplying one of our custom selectors. The custom Gestalt routine returns the address of whichever global variable corresponds to the supplied selector. The CF unit which issued the  custom Gestalt call could then use or update whatever resides at that address in memory as needed.
  66.  
  67. Secondly, the INIT also loads our custom WAPI ShowWindowa and CloseWindow patches, which are stored as separate code resources (of type 'WAPI') in the CF Extension file. Our WAPI resources are also marked with both the 'System' and 'Locked' flags, for reasons similar to those for the INIT code resource. The INIT first saves the addresses of the original ShowWindow and CloseWindow routines from the trap-table, then inserts the addresses of our loaded WAPI routines in memory into the trap-table, to serve as their {partial} replacements. Since the INIT has saved the addresses of the original routines in global variables, our custom WAPI routines could obtain those addresses via the above-mentioned Gestalt mechanism whenever they need to, and use them to call the original routines.
  68.  
  69. The custom WAPI ShowWindow patch
  70.  
  71. As mentioned earlier, the custom ShowWindow patch does the first "half" of the work. It is triggered whenever any window is displayed, but it actually does its own special thing only when a window is owned by the Finder and  corresponds to a folder; otherwise, it simply falls through to the standard ShowWindow routine.
  72.  
  73. When the custom ShowWindow patch is triggered for an opening window, it performs a quick check to determine whether the supplied window "reference" denotes one of the Finder's folder windows. It does this by verifying firstly that the Finder is the current process (whether foreground or background), and secondly that the supplied window is a standard document window equipped with a close-box, zoom-box, etc. If the window satisfies these tests, it's assumed to be a Finder folder window. Next, the custom ShowWindow patch performs the following sequence of tasks:
  74.  
  75. • It obtains information (about the location of the corresponding folder) from the window's "meta-data", by calling the GetWindowProxyFSSpec routine, supplying it in turn with the window-reference and a pointer to an FSSpec record in which to return the results.
  76.  
  77. • If it succesfully obtained the location information, it stores that information into a shared memory cache, "keyed" by the supplied window-reference.
  78.  
  79. Of course, regardless of whether or not the supplied window is a Finder folder window, the custom ShowWindow patch always calls the original ShowWindow API routine to actually display the window.
  80.  
  81. The custom WAPI CloseWindow patch
  82.  
  83. As mentioned earlier, the custom CloseWindow patch does the second "half" of the work. It is triggered whenever any window is closed, but it too (like the ShowWindow patch) actually does its own special thing only when a window is owned by the Finder and  corresponds to a folder; otherwise, it simply falls through to the standard CloseWindow routine.
  84.  
  85. When the custom CloseWindow patch is triggered for a closing window, it too (like the ShowWindow patch) performs a quick check to determine whether the supplied window "reference" denotes one of the Finder's folder windows. It does this by verifying firstly that the Finder is the current process (whether foreground or background), and secondly that the supplied window is a standard document window equipped with a close-box, zoom-box, etc. If the window satisfies these tests, it's assumed to be a Finder folder window. Next, the custom CloseWindow patch performs the following sequence of tasks:
  86.  
  87. • It obtains the previously-stored information (about the location of the corresponding folder) from the shared memory cache, using the specified window-reference as the "key". [NOTE: At this point, if the location information could not be obtained from the cache for any reason — or if an explicit or implicit bypass condition exists (as described in the 'Closed Folders Extension: Operation' section of the CF 'Read Me' document) — then it skips the remaining steps and jumps directly to the final "cache-cleanup" step.] 
  88.  
  89. • It builds a new alias record describing the closing (or "target") folder.
  90.  
  91. • It creates a new file in the alias-cache folder, giving it the same name as the target folder and assigning it the creator-code 'MACS' and the type-code 'fdrp'. (If a file with that name already exists, it is simply reused for the next step; note that this destroys the folder-location information which it previously contained.)
  92.  
  93. • It stores the alias record's data as a resource in the file's resource fork, assigning it a resource-type of 'alis' and a resource-ID of 0.
  94.  
  95. • It sets the 'kIsAlias' bit in the file's "Finder flags", which reside in the host volume's "catalogue" or disk directory. [The 'kIsAlias' bit is the high-order bit (i.e., bit 15) of the short integer in which Finder flags are stored.]
  96.  
  97. [BTW, the last three steps above closely mimic the official method that is used by the Finder itself to create alias files for target folders, as partially documented in Apple's old Technical Note 'TB 535 - Finder Q&As', and also extensively discussed in the 'comp.sys.mac.programmer' newsgroup digest (Vol 3, No. 101).]
  98.  
  99. • Finally, it deletes the location information from the shared memory cache (if it had been retrieved successfully).
  100.  
  101. Of course, regardless of whether or not the supplied window is a Finder folder window, the custom CloseWindow patch always calls the original CloseWindow API routine to actually close the window.
  102.  
  103.                                                                                                                                                    
  104. Afterword
  105.  
  106. That's it! I hope that the foregoing explanation was of some interest. In addition, at some point in the near future — time permitting [and code-cleanup succeeding ;-)] — the source code for the CF utility will be included in the downloadable CF archive, and/or posted on our web site.
  107.  
  108. Regards,
  109.  
  110. —Paul